package ar.fiuba.logger;

import java.util.List;

/**
 * The Class Logger.
 * 
 * This class implements the logger.
 */
public class Logger {

  /** The reporters. */
  private List<LogReporter> reporters;

  /** The filters. */
  private List<LogFilter> filters;

  /** The threshold. */
  private LogLevel threshold;

  /** The separator. */
  private String separator;

  /** The name. */
  private String name;

  /**
   * Instantiates a new logger.
   *
   * @param name the name
   * @param config the config
   */
  public Logger(String name, LogConfig config) {
    this.name = name;
    this.applyConfig(config);
  }

  /**
   * Internal Logger method.
   * 
   * @param message the message
   * @param level the level
   * @param exception the exception
   */
  private void _log(String message, LogLevel level, Throwable exception) {

    /*
     * Every public method that wants to log a message should call this method directly. For
     * example: logger.info(msg) -> _log(msg,INFO,null) This is because environment information is
     * loaded inside this private method and there must be exactly to stack levels between the real
     * function caller the logger and this private method.
     */

    /*
     * LogEnvironmentInformation object created with stack depth 2, so information from caller
     * function is used. Remember normally our stack will be like this: + Caller Method
     * +->logger.info( msg ) (using info as an example, but could be debug, warn, etc.)
     * +-->logger.log( msg, INFO )
     * 
     * So we need to go up two stack levels to reach the caller environment information.
     */
    int stackDepth = 2;
    LogEnvironmentInformation callerInfo = new LogEnvironmentInformation(stackDepth);

    LogMessage logMessage = new LogMessage(message, level, callerInfo, this.getName(), exception);

    if (this.showMessage(logMessage)) {
      for (LogReporter reporter : reporters) {
        reporter.report(logMessage);
      }
    }
  }

  /**
   * Apply config.
   * 
   * @param config the config
   */
  public void applyConfig(LogConfig config) {
    separator = config.separator;
    threshold = config.threshold;

    reporters = config.reporters;

    filters = config.filters;
  }

  /**
   * Debug.
   * 
   * @param message the message
   */
  public void debug(String message) {
    this._log(message, LogLevel.DEBUG, null);
  }

  /**
   * Debug.
   * 
   * @param message the message
   * @param exception the exception
   */
  public void debug(String message, Throwable exception) {
    this._log(message, LogLevel.DEBUG, exception);
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Logger other = (Logger) obj;
    if (filters == null) {
      if (other.filters != null)
        return false;
    } else if (!filters.equals(other.filters))
      return false;
    if (name == null) {
      if (other.name != null)
        return false;
    } else if (!name.equals(other.name))
      return false;
    if (reporters == null) {
      if (other.reporters != null)
        return false;
    } else if (!reporters.equals(other.reporters))
      return false;
    if (separator == null) {
      if (other.separator != null)
        return false;
    } else if (!separator.equals(other.separator))
      return false;
    if (threshold != other.threshold)
      return false;
    return true;
  }

  /**
   * Error.
   * 
   * @param message the message
   */
  public void error(String message) {
    this._log(message, LogLevel.ERROR, null);
  }

  /**
   * Error.
   * 
   * @param message the message
   * @param exception the exception
   */
  public void error(String message, Throwable exception) {
    this._log(message, LogLevel.ERROR, exception);
  }

  /**
   * Fatal.
   * 
   * @param message the message
   */
  public void fatal(String message) {
    this._log(message, LogLevel.FATAL, null);
  }

  /**
   * Fatal.
   * 
   * @param message the message
   * @param exception the exception
   */
  public void fatal(String message, Throwable exception) {
    this._log(message, LogLevel.FATAL, exception);
  }

  /**
   * Gets the name.
   * 
   * @return the name
   */
  public String getName() {
    return name;
  }

  /**
   * Gets the separator.
   * 
   * @return the separator
   */
  public String getSeparator() {
    return separator;
  }

  /**
   * Gets the current threshold level.
   * 
   * @return the threshold
   */
  public LogLevel getThreshold() {
    return threshold;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((filters == null) ? 0 : filters.hashCode());
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + ((reporters == null) ? 0 : reporters.hashCode());
    result = prime * result + ((separator == null) ? 0 : separator.hashCode());
    result = prime * result + ((threshold == null) ? 0 : threshold.hashCode());
    return result;
  }

  /**
   * Info.
   * 
   * @param message the message
   */
  public void info(String message) {
    this._log(message, LogLevel.INFO, null);
  }

  /**
   * Info.
   * 
   * @param message the message
   * @param exception the exception
   */
  public void info(String message, Throwable exception) {
    this._log(message, LogLevel.INFO, exception);
  }

  /**
   * Log.
   * 
   * @param message the message
   * @param level the level
   * @param exception the exception
   */
  public void log(String message, LogLevel level, Throwable exception) {
    this._log(message, level, exception);
  }

  /**
   * Show message.
   * 
   * @param logMessage the log message
   * @return the boolean
   */
  private Boolean showMessage(LogMessage logMessage) {
    for (LogFilter filter : filters) {
      if (!filter.allow(logMessage))
        return false;
    }

    return true;
  }

  /**
   * Trace.
   * 
   * @param message the message
   */
  public void trace(String message) {
    this._log(message, LogLevel.TRACE, null);
  }

  /**
   * Trace.
   * 
   * @param message the message
   * @param exception the exception
   */
  public void trace(String message, Throwable exception) {
    this._log(message, LogLevel.TRACE, exception);
  }

  /**
   * Warn.
   * 
   * @param message the message
   */
  public void warn(String message) {
    this._log(message, LogLevel.WARN, null);
  }

  /**
   * Warn.
   * 
   * @param message the message
   * @param exception the exception
   */
  public void warn(String message, Throwable exception) {
    this._log(message, LogLevel.WARN, exception);
  }
}
